﻿
namespace FolderSync
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    public class Mirrorer : ITranslater
    {
        public IFileFilter filter { get; set; }
        public string WatchFolderName { get; set; }
        public string LogTag { get; set; }
        public Func<string, string> TranslatePath { get; set; }
        public Func<string, string> ReverseTranslatePath { get; set; }
        public bool Destructive { get; set; }
        public Syncer Syncer { get; set; }

        public INotifier Notifier { get; set; }
        private bool _threadRunning;
        private string _currentFileName;
        public bool IsRunning { get { return _threadRunning; } }

        public delegate void StatusChangedHandler(Mirrorer sender, string status);
        public event StatusChangedHandler StatusChanged;

        public void Stop()
        {
            _threadRunning = false;
        }

        private class MirrorInterruptException : Exception
        {
            public static bool TrowIfFalse(bool b) { if (!b) throw new MirrorInterruptException(); return true; }
        };
        public void Run()
        {
            _threadRunning = true;
            Notifier.Message("Mirroring {0} Start: «{1}» to «{2}»", LogTag, WatchFolderName, TranslatePath(WatchFolderName));
            var WatchFolder = new DirectoryInfo(WatchFolderName);
            if (!WatchFolder.Exists)
            {
                Notifier.Error("Mirroring {0}: Source folder «{1}» not found", LogTag, WatchFolderName);
                return;
            }

            var results = new SyncStats();
            if (Destructive) results += Mirror(new DirectoryInfo(TranslatePath(WatchFolderName)), filter, delete: true);
            results += Mirror(WatchFolder, filter, delete: false);
            Syncer.List(onDebugOnly: true);
            var msg = string.Format("Mirroring {0}: «{1}» to «{2}», {3} files totalling {4} in {5} folders.",
                LogTag,
                WatchFolderName, TranslatePath(WatchFolderName),
                results.TotalFiles, results.TotalBytes.FormatByteSize(), results.TotalFolders);
            Notifier.Message(msg);
            StatusChanged(this, msg);
            _threadRunning = false;
            Notifier.Message("Mirrorer {0} Done: «{1}» to «{2}»", LogTag, WatchFolderName, TranslatePath(WatchFolderName));
        }
        SyncStats Mirror(DirectoryInfo Source, IFileFilter filter, bool delete)
        {
            Notifier.Message("Mirroring {0}: Recursing into {1}", LogTag, Source.FullName);
            StatusChanged(this, string.Format("Mirroring {0}: Recursing into {1}", LogTag, Source.FullName));
            _currentFileName = Source.FullName;
            var result = new SyncStats();
            result.CountCheckedFolder(Source);
            if (!_threadRunning) return result;
            try
            {
                var files = Source.EnumerateFiles()
                        .Each(f => MirrorInterruptException.TrowIfFalse(_threadRunning))
                        .Each(f => { _currentFileName = "Enumerate " + f.FullName; return true; })
                        .Where(f => filter.MatchFile(f.Name));
                if (delete)
                {
                    foreach (var f in files
                        .Each(f => result.CountCheckedFiles(f))
                        .Each(f => MirrorInterruptException.TrowIfFalse(_threadRunning))
                        .Each(f => { _currentFileName = "Check Delete " + f.FullName; return true; })
                        .Select(f => new FileInfo(ReverseTranslatePath(f.FullName)))
                        .Where(f => !f.Exists)
                        )
                    {
                        result.CountDeletedFile(f);
                        Syncer.Enqueue(
                            new FileEvent(TranslatePath) { ChangeType = CompoundWatcherChangeTypes.Deleted, FullPath = f.FullName }, 0);
                    }
                }
                else
                {
                    foreach (var f in files
                        .Each(f => result.CountCheckedFiles(f))
                        .Each(f => MirrorInterruptException.TrowIfFalse(_threadRunning))
                        .Each(f => { _currentFileName = "Check Mirror " + f.FullName; return true; })
                        .Where(f =>
                            {
                                var df = new FileInfo(TranslatePath(f.FullName));
                                return (!df.Exists
                                    //|| df.CreationTime != f.CreationTime
                                    //|| df.LastWriteTime != f.LastWriteTime
                                    || df.Length != f.Length);
                            }))
                    {
                        result.TotalFolders = 1;
                        result.TotalBytes += (long)f.Length;
                        result.TotalFiles++;
                        Syncer.Enqueue(
                            new FileEvent(TranslatePath) { ChangeType = CompoundWatcherChangeTypes.Changed, FullPath = f.FullName }, 0);
                    }
                }
            }
            catch (Exception e)
            {
                if (e is MirrorInterruptException) return result;
                Notifier.Error("Mirroring {0}: Exception {1} while listing {2}: {3}.", LogTag, e.GetType().Name, Source.FullName, e.Message);
            }
            try
            {
                foreach (var d in Source.EnumerateDirectories().Where(f => MirrorInterruptException.TrowIfFalse(_threadRunning)))
                {
                    if (!_threadRunning) return result;
                    if (!filter.MatchFolder(d.FullName)) Notifier.Warning("Mirroring {0}: Filtered out {1}", LogTag, d.FullName);
                    else if (d.IsReparsePoint()) Notifier.Warning("Mirroring {0}: Attribute mismatch {1}", LogTag, d.FullName);
                    else
                    {
                        result += Mirror(d, filter, delete);
                        if (!_threadRunning)
                        {
                            return result;
                        }
                    }
                }
                if (delete && !Source.EnumerateFileSystemInfos().Any())
                {
                    Syncer.Enqueue(
                        new FileEvent(TranslatePath) { ChangeType = CompoundWatcherChangeTypes.Deleted, FullPath = ReverseTranslatePath(Source.FullName) }
                    , 0);
                }
            }
            catch (Exception e)
            {
                if (e is MirrorInterruptException) return result;
                Notifier.Error("Mirroring {0}: Exception {1} while recursing into {2}: {3}.", LogTag, e.GetType().Name, Source.FullName, e.Message);
            }
            return result;
        }
        public string Status
        {
            get
            {
                return _currentFileName.Replace("{", "{{").Replace("}", "}}");
            }
        }
    }
}
